//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#pragma once

#include "../CSource.h"
#include "GeneratorUtils.h"
#include "GeneratorNativeUtils.h"

namespace Noise
{
	
	const real DEFAULT_SELECT_EDGE_FALLOFF = 0.0;
	const real DEFAULT_SELECT_LOWER_BOUND = -1.0;
	const real DEFAULT_SELECT_UPPER_BOUND = 1.0;



	//
	// Function Source for source selection.
	//

	class CSourceSelect : public CSource
	{
	// types
	protected:
		typedef CSourceSelect	Self;


	// variables
	protected:
		CSourcePtr	_src0,
					_src1,
					_src2;
		real		_edgeFalloff;	// Edge-falloff value.
		real		_lowerBound;	// Lower bound of the selection range.
		real		_upperBound;	// Upper bound of the selection range.
		
		static const
			ESource	_sourceType = SELECT;


	// methods
	public:
		CSourceSelect () :
			_edgeFalloff(DEFAULT_SELECT_EDGE_FALLOFF),
			_lowerBound(DEFAULT_SELECT_LOWER_BOUND),
			_upperBound(DEFAULT_SELECT_UPPER_BOUND)
		{}

		~CSourceSelect ()
		{}

		ESourceState GetState () const
		{
			return (_src0.IsNotNull() and _src1.IsNotNull() and _src2.IsNotNull()) ?
					SOURCE_COMPLETE : UNCOMPLETE_ATTACHMENT_SOURCE;
		}
		
		virtual void Delete ()
		{
			_src0 = null;
			_src1 = null;
			_src2 = null;
		}

		virtual EResult GetInteger (ESourceQuery type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case SOURCE_STATE :
					v[0] = GetState();
					return OK;

				case SOURCE_TYPE :
					v[0] = _sourceType;
					return OK;

				case SOURCES_COUNT :
				{
					int	count0 = 0,
						count1 = 0,
						count2 = 0;

					_src0->GetInteger( SOURCES_COUNT, &count0, 1 );
					_src1->GetInteger( SOURCES_COUNT, &count1, 1 );
					_src2->GetInteger( SOURCES_COUNT, &count2, 1 );

					v[0] = count0 + count1 + count2 + 1;
					return OK;
				}

				case INPUT_SOURCES :
					v[0] = 3;
					return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, CSourcePtr src)
		{
			switch ( type )
			{
				case SOURCE_0 :	_src0 = src;	return OK;
				case SOURCE_1 :	_src1 = src;	return OK;
				case SOURCE_2 :	_src2 = src;	return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult SetArg (EArgument type, const real *v, int count)
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_EDGE_FALLOFF :	_edgeFalloff = v[0];	return OK;
				case ARG_LOWER_BOUND :	_lowerBound = v[0];		return OK;
				case ARG_UPPER_BOUND :	_upperBound = v[0];		return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult GetArg (EArgument type, CSourcePtr &src) const
		{
			switch ( type )
			{
				case SOURCE_0 :	src = _src0;	return OK;
				case SOURCE_1 :	src = _src1;	return OK;
				case SOURCE_2 :	src = _src2;	return OK;
			}
			return INVALID_ENUM;
		}

		virtual EResult GetArg (EArgument type, real *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_EDGE_FALLOFF :	v[0] = _edgeFalloff;	return OK;
				case ARG_LOWER_BOUND :	v[0] = _lowerBound;		return OK;
				case ARG_UPPER_BOUND :	v[0] = _upperBound;		return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual CSourcePtr Clone () const override
		{
			Self *	src = new Self();

			src->_edgeFalloff	= _edgeFalloff;
			src->_lowerBound	= _lowerBound;
			src->_upperBound	= _upperBound;

			return src;
		}
		
		virtual void PreBuild (const TPreBuildData &data) override
		{
			data.Push( _src0.ptr() );
			data.Push( _src1.ptr() );
			data.Push( _src2.ptr() );
		}

		virtual bool BuildFunction (TFunctionData &data) override
		{
			Utils::NoiseGenFunctions( data );
			UpdateParams();

			if ( data.AddSourceType( _sourceType ) )
			{
				data.source << TOSTRING(
					real Select (real3 vPoint, real controlValue, real value0, real value1,\n
								 real _lowerBound, real _upperBound, real _edgeFalloff)\n
					{\n
						real alpha;\n
						\n
						if ( _edgeFalloff > 0.0 )\n
						{\n
							if ( controlValue < (_lowerBound - _edgeFalloff) ) {\n
								return value0;\n
							} else\n
							if ( controlValue < (_lowerBound + _edgeFalloff) )\n
							{\n
								real lowerCurve = (_lowerBound - _edgeFalloff);\n
								real upperCurve = (_lowerBound + _edgeFalloff);\n
								alpha = SCurve3( (controlValue - lowerCurve) / (upperCurve - lowerCurve) );\n
								return mix( value0, value0, alpha );\n
							}\n
							else\n
							if ( controlValue < (_upperBound - _edgeFalloff) )\n
								return value1;\n
							else\n
							if ( controlValue < (_upperBound + _edgeFalloff) ) {\n
								real lowerCurve = (_upperBound - _edgeFalloff);\n
								real upperCurve = (_upperBound + _edgeFalloff);\n
								alpha = SCurve3 ( (controlValue - lowerCurve) / (upperCurve - lowerCurve) );\n
								return mix( value1, value0, alpha );\n
							}\n
							else\n
								return value0;\n
						}\n
						else\n
						{\n
							if ( controlValue < _lowerBound or controlValue > _upperBound )\n
								return value0;\n
							else\n
								return value1;\n
						}\n
					}\n\n
				);
			}

			CSource::BuildFunctionHeader( data.source, _id )
				<< " ( Select( vPoint, ";
			CSource::BuildFunctionCall( data.source, _src2->GetSourceId() ) << ", ";
			CSource::BuildFunctionCall( data.source, _src0->GetSourceId() ) << ", ";
			CSource::BuildFunctionCall( data.source, _src1->GetSourceId() ) << ", "
				<< _lowerBound << ", " << _upperBound << ", " << _edgeFalloff << " ) )\n";

			return true;
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		Real NativeFunction (const TVec<Real,3> &vPoint)
		{
			using namespace NativeUtils;

			UpdateParams();

			Real controlValue = (_src2.ptr()->*NativeFunction)( vPoint );
			Real alpha;

			if ( _edgeFalloff > 0.0 )
			{
				if ( controlValue < (_lowerBound - _edgeFalloff) ) {
					return (_src0.ptr()->*NativeFunction)( vPoint );
				} else
				if ( controlValue < (_lowerBound + _edgeFalloff) )
				{
					Real lowerCurve = (_lowerBound - _edgeFalloff);
					Real upperCurve = (_lowerBound + _edgeFalloff);
					alpha = SCurve3( (controlValue - lowerCurve) / (upperCurve - lowerCurve) );
					return Mix( (_src0.ptr()->*NativeFunction)( vPoint ),
								(_src1.ptr()->*NativeFunction)( vPoint ), alpha );
				}
				else
				if ( controlValue < (_upperBound - _edgeFalloff) )
					return (_src1.ptr()->*NativeFunction)( vPoint );
				else
				if ( controlValue < (_upperBound + _edgeFalloff) ) {
					Real lowerCurve = (_upperBound - _edgeFalloff);
					Real upperCurve = (_upperBound + _edgeFalloff);
					alpha = SCurve3 ( (controlValue - lowerCurve) / (upperCurve - lowerCurve) );
					return Mix( (_src1.ptr()->*NativeFunction)( vPoint ),
								(_src0.ptr()->*NativeFunction)( vPoint ), alpha );
				}
				else
					return (_src0.ptr()->*NativeFunction)( vPoint );
			}
			else
			{
				if ( controlValue < _lowerBound or controlValue > _upperBound )
					return (_src0.ptr()->*NativeFunction)( vPoint );
				else
					return (_src1.ptr()->*NativeFunction)( vPoint );
			}
		}

		virtual float32 NativeFunction32 (const fvec3 &vPoint) override
		{
			return NativeFunction< float32, &CSource::NativeFunction32 >( vPoint );
		}

		virtual float64 NativeFunction64 (const dvec3 &vPoint) override
		{
			return NativeFunction< float64, &CSource::NativeFunction64 >( vPoint );
		})

		void UpdateParams ()
		{
			real boundSize = _upperBound - _lowerBound;
			_edgeFalloff = (_edgeFalloff > boundSize / 2) ? boundSize / 2 : _edgeFalloff;
		}

		static bool CreateSource (CSourcePtr &src, CContext *context, ESource type)
		{
			ASSERT( type == _sourceType );

			src = new Self();
			return true;
		}
	};


}	// Noise